package com.ouyunc.log.aop;

import com.ouyunc.common.constant.CommonConstant;
import com.ouyunc.common.context.ThreadLocalContextHolder;
import com.ouyunc.common.utils.IPUtil;
import com.ouyunc.common.utils.RequestUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.UUID;

/**
 * @Author fangzhenxun
 * @Description 全局日志aop，记录请求的 服务名称application-name，请求ip,请求路径url,请求方式get/post..., 请求入参，开始请求时间，结束请求时间，耗时，返回参数
 * @Date 2020/12/21 15:45
 **/
@Aspect
@Component
public class TtlLogAspect {
    private static final Logger log = LoggerFactory.getLogger(TtlLogAspect.class);


    @Autowired
    private Environment environment;

    /**
     * 切点
     */
    @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping) || @annotation(org.springframework.web.bind.annotation.PutMapping)")
    public void pointcut(){};


    /**
     * @Author fangzhenxun
     * @Description  日志环绕通知, 注解@xxxMapping 继承 注解@RequestMapping
     * @Date 2020/12/21 15:52
     **/
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        recordLog();
        Object proceed = proceedingJoinPoint.proceed();
        long end = System.currentTimeMillis();
        log.info("当前请求信息:\r\n client-ip: {}\r\n request-uri: {}\r\n request-cost-time: {}ms", MDC.get(CommonConstant.LOG_CLIENT_IP), MDC.get(CommonConstant.LOG_REQUEST_URI),(end-start));
        MDC.clear();
        return proceed;
    }

    /**
     * 最终通知：是在目标方法执行之后执行的通知，目标方法正常和异常都会回调该方法
     */
    @AfterThrowing(value = "pointcut()",throwing = "ex")
    public void logThrowing(Exception ex){
        log.error("异常信息：{}", ex.getMessage());
        ex.printStackTrace();
        MDC.clear();
    }


    // @todo 这里需要进行处理填充需要的请求头
    public void recordLog(){
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest currentRequest = servletRequestAttributes.getRequest();
        Map<String, String> headers = RequestUtil.getHeaders(currentRequest);
        Map<String, Object> parameters = RequestUtil.getParameters(currentRequest);
        // @TODO 注意：这里不存 HttpServletRequest 是有原因的，在（多线程中会有问题），因为HttpServletRequest在主线程退出后会被主线程销毁，里面的属性什么都没有了，也就
        // @TODO 也就取不到里面的值，测试过,如果需要在多线程（子线程获取请求头中的数据可以使用这种方式存储和获取）
        ThreadLocalContextHolder.set(CommonConstant.CURRENT_REQUEST_HEADERS, headers);
        ThreadLocalContextHolder.set(CommonConstant.CURRENT_REQUEST_PARAMS, parameters);
        // 从请求头获取traceId，并判断是否存在日志traceId
        String traceId = currentRequest.getHeader(CommonConstant.LOG_TRACE_ID);
        String spanId = currentRequest.getHeader(CommonConstant.LOG_SPAN_ID);
        // 父spanId，也就是上级的spanId
        MDC.put(CommonConstant.LOG_PARENT_SPAN_ID, spanId);
        MDC.put(CommonConstant.LOG_SPAN_ID, UUID.randomUUID().toString());
        // 如果没有日志跟踪traceId 则重新生成一个传递下去
        if (StringUtils.isBlank(traceId)) {
            traceId = UUID.randomUUID().toString();
        }
        MDC.put(CommonConstant.LOG_TRACE_ID, traceId);

        String clientIp = IPUtil.getIpAddr(currentRequest);
        MDC.put(CommonConstant.LOG_CLIENT_IP, clientIp);

        String serverIp = currentRequest.getServerName();
        MDC.put(CommonConstant.LOG_SERVER_IP, serverIp);

        String requestUri = currentRequest.getServletPath();
        MDC.put(CommonConstant.LOG_REQUEST_URI, requestUri);

        String applicationName = environment.getProperty(CommonConstant.APPLICATION_NAME_KEY);
        MDC.put(CommonConstant.LOG_SERVER_NAME, applicationName);

    }
 
}